/*
# Copyright (c) 2024 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
*/

#include "ohos_desktop.h"

#include <multimedia/player_framework/native_avscreen_capture_errors.h>
#include <native_buffer/native_buffer.h>
#include "hilog/log.h"

#include "egl_render_context.h"
#include "ohos_eglContext_manage.h"
#include "util.h"

namespace webrtc {
namespace ohos {

std::atomic<bool>  OhosDesktop::isRunning_ {false};
float OhosDesktop::matrix_[] {0};
void OhosDesktop::OnError(OH_AVScreenCapture *capture, int32_t errorCode, void *userData) {
    (void)capture;
    (void)errorCode;
    (void)userData;
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "errorCode = %{public}d", errorCode);
}

void OhosDesktop::OnStateChange(struct OH_AVScreenCapture *capture, OH_AVScreenCaptureStateCode stateCode, 
        void *userData) {
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "stateCode = %{public}d", stateCode);
    OhosDesktop *desktop = static_cast<OhosDesktop *>(userData);
    desktop->SetStateCode(stateCode);
}

void OhosDesktop::OnBufferAvailable(OH_AVScreenCapture *capture, OH_AVBuffer *buffer, 
        OH_AVScreenCaptureBufferType bufferType, int64_t timestamp, void *userData) {
    OhosDesktop *desktop = static_cast<OhosDesktop *>(userData);
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "OnBufferAvailable enter");
    if (bufferType == OH_SCREEN_CAPTURE_BUFFERTYPE_VIDEO) {
        return;
    } else if (bufferType == OH_SCREEN_CAPTURE_BUFFERTYPE_AUDIO_INNER) {
        // 处理内录buffer
        return;
    } else if (bufferType == OH_SCREEN_CAPTURE_BUFFERTYPE_AUDIO_MIC) {
        // 处理麦克风buffer
        return;
    }
}

int32_t OhosDesktop::Create() {
    // 只初始化一次
    if (capture_ != nullptr) {
        return 0;
    }
    capture_ = OH_AVScreenCapture_Create();
    if (capture_ == nullptr) {
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "OH_AVScreenCapture_Create failed");
        return -1;
    }
    // 设置回调
    OH_AVSCREEN_CAPTURE_ErrCode ret = OH_AVScreenCapture_SetErrorCallback(capture_, OnError, this);
    if (ret != AV_SCREEN_CAPTURE_ERR_OK) {
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "OH_AVScreenCapture_SetErrorCallback failed");
        return -1;
    }

    ret = OH_AVScreenCapture_SetStateCallback(capture_, OnStateChange, this);
    if (ret != AV_SCREEN_CAPTURE_ERR_OK) {
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "OH_AVScreenCapture_SetStateCallback failed");
        return -1;
    }

    ret = OH_AVScreenCapture_SetDataCallback(capture_, OnBufferAvailable, this);
    if (ret != AV_SCREEN_CAPTURE_ERR_OK) {
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "OH_AVScreenCapture_SetDataCallback failed");
        return -1;
    }

    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "create and set callback"); 
    return 0;
}

int32_t OhosDesktop::Config() {
    OH_AudioCaptureInfo micCaptureInfo = {.audioSampleRate = 0, .audioChannels = 0, .audioSource = OH_MIC};
    OH_VideoCaptureInfo videoCaptureInfo = {
        .videoFrameWidth = width_, .videoFrameHeight = height_, .videoSource = OH_VIDEO_SOURCE_SURFACE_RGBA};

    OH_AudioInfo audioInfo = {
        .micCapInfo = micCaptureInfo, 
    };
    OH_VideoEncInfo videoEncInfo = {.videoCodec = OH_H264, .videoBitrate = 2000000, .videoFrameRate = 30};
    OH_VideoInfo videoInfo = {.videoCapInfo = videoCaptureInfo, .videoEncInfo = videoEncInfo};

    OH_AVScreenCaptureConfig config = {.captureMode = OH_CAPTURE_HOME_SCREEN,
                                       .dataType = OH_ORIGINAL_STREAM,
                                       .audioInfo = audioInfo,
                                       .videoInfo = videoInfo};

    int32_t ret = OH_AVScreenCapture_Init(capture_, config);
    if (ret != AV_SCREEN_CAPTURE_ERR_OK) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosDesktop", "OH_AVScreenCapture_Init failed");
        return -1;
    }
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "Config end");
    return 0;
}

int32_t OhosDesktop::Start() {
    if (Create() != 0) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosDesktop", "Create failed");
        return -1;
    }
    if (Config() != 0) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosDesktop", "Config failed");
        return -1;
    }

    if (InitRenderContext() == false) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosDesktop", "InitRenderContext failed");
        return -1;
    }

    if (CreateNativeImage() == false) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosDesktop", "CreateNativeImage failed");
        return -1;
    }

    if (CreateNativeWindow() == false) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosDesktop", "CreateNativeWindow failed");
        return -1;
    }

    int32_t ret = OH_AVScreenCapture_StartScreenCaptureWithSurface(capture_, nativeWindow_);
    if (ret != AV_SCREEN_CAPTURE_ERR_OK) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosDesktop",
                     "OH_AVScreenCapture_StartScreenCaptureWithSurface failed");
        return -1;
    }
    isRunning_.store(true);
    OhosEGLContextManage::GetInstance().GetConditionVariable().notify_all();
    return 0;
} 

int32_t OhosDesktop::Stop() {
    if (capture_ && stateCode_ == OH_SCREEN_CAPTURE_STATE_STARTED) {
        int32_t ret = OH_AVScreenCapture_StopScreenCapture(capture_);
        if (ret != AV_SCREEN_CAPTURE_ERR_OK) {
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosDesktop", "OH_AVScreenCapture_StopScreenCapture failed");
        }
        stateCode_ = OH_SCREEN_CAPTURE_STATE_STOPPED_BY_USER;
    }
    isRunning_.store(false);
    OhosEGLContextManage::GetInstance().GetConditionVariable().notify_all();
    DestroyNativeWindow();
    DestroyNativeImage();
    DestroyRenderContext();

    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "Stop() end");
    return 0;
}
 
int32_t OhosDesktop::SetStateCode(OH_AVScreenCaptureStateCode stateCode) {
    stateCode_ = stateCode;
    return 0;
}

void OhosDesktop::OnNativeImageFrameAvailable(void *data) {
    OhosDesktop* desktop = static_cast<OhosDesktop*>(data);
    desktop->FrameAvailable();
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "OnNativeImageFrameAvailable called");
}

int32_t OhosDesktop::FrameAvailable() {
    int32_t ret;
    {
        std::lock_guard<std::mutex> lock(OhosEGLContextManage::GetInstance().GetMutex());
        if (isRunning_.load() == false)
            return -1;
        renderContext_->MakeCurrent(eglSurface_);
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "FrameAvailable MakeCurrent %{public}s ",
                     GetEglErrorString());

        ret = OH_NativeImage_UpdateSurfaceImage(nativeImage_);
         OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "FrameAvailable OH_NativeImage_UpdateSurfaceImage %{public}s",
                      GetGLErrorString());
         OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "FrameAvailable ImageTextureID = %{public}d ", nativeImageTextureID_);

        renderContext_->MakeNoCurrent();
         OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "FrameAvailable MakeEmptyCurrent %{public}s ",
                      GetEglErrorString());
    }

    if (ret != 0) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosDesktop",
                     "OH_NativeImage_UpdateSurfaceImage failed, ret: %{public}d", ret);
        return -1;
    }
    OH_NativeImage_GetTransformMatrix(nativeImage_, matrix_);
    OhosEGLContextManage::GetInstance().PushTextureID(nativeImageTextureID_);
    OhosEGLContextManage::GetInstance().GetConditionVariable().notify_all();
    OhosEGLContextManage::GetInstance().AddDesktopCnt();
    OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosDesktop",
                                     "GetDesktopCnt: %{public}d", OhosEGLContextManage::GetInstance().GetDesktopCnt());
    return 0;
}

bool OhosDesktop::InitRenderContext() {
    renderContext_ = std::make_unique<EglRenderContext>();
    if (renderContext_->Init() == false) {
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "EglRenderContext Init() failed");
        return false;
    }

    OhosEGLContextManage::GetInstance().SetEGLContext(renderContext_->GetEGLContext());
    EGLint pBufferAttributes[] = {EGL_WIDTH, width_, EGL_HEIGHT, height_, EGL_NONE};
    eglSurface_ = renderContext_->CreateEglSurface(pBufferAttributes);
    if (eglSurface_ == EGL_NO_SURFACE) {
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "CreateEglSurface %{public}s ", GetEglErrorString());
        return false;
    }

    return true;
}

int32_t OhosDesktop::DestroyRenderContext() {
    if (eglSurface_ != EGL_NO_SURFACE) {
        renderContext_->DestroyEglSurface(eglSurface_);
        eglSurface_ = EGL_NO_SURFACE;
    }
    OhosEGLContextManage::GetInstance().SetEGLContext(EGL_NO_CONTEXT);
    renderContext_.reset();
    return 0;
}

bool OhosDesktop::CreateNativeWindow() {
    std::lock_guard<std::mutex> lock(OhosEGLContextManage::GetInstance().GetMutex());
    renderContext_->MakeCurrent(eglSurface_);
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "CreateNativeWindow MakeCurrent %{public}s ",
                 GetEglErrorString());

    nativeWindow_ = OH_NativeImage_AcquireNativeWindow(nativeImage_);
    if (nativeWindow_ == nullptr) {
        renderContext_->MakeNoCurrent();
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosDesktop", "OH_NativeImage_AcquireNativeWindow failed");
        return false;
    }

    int32_t ret = OH_NativeWindow_NativeWindowHandleOpt(nativeWindow_, SET_BUFFER_GEOMETRY, width_, height_);
    if (ret != 0) {
        renderContext_->MakeNoCurrent();
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosDesktop", "OH_NativeWindow_NativeWindowHandleOpt failed");
        return false;
    }

    renderContext_->MakeNoCurrent();
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "CreateNativeWindow MakeEmptyCurrent %{public}s ",
                 GetEglErrorString());
    return true;
}

int32_t OhosDesktop::DestroyNativeWindow() {
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "DestroyNativeWindow started");
    if (nativeWindow_ != nullptr) {
        OH_NativeWindow_DestroyNativeWindow(nativeWindow_);
        nativeWindow_ = nullptr;
    }
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "DestroyNativeWindow end");
    return 0;
}

bool OhosDesktop::CreateNativeImage() {
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "CreateNativeImage started");

    std::lock_guard<std::mutex> lock(OhosEGLContextManage::GetInstance().GetMutex());
    renderContext_->MakeCurrent(eglSurface_);
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "CreateNativeImage MakeCurrent %{public}s ",
                 GetEglErrorString());

    glGenTextures(1, &nativeImageTextureID_);
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "textureID = %{public}u", nativeImageTextureID_);
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "glGenTextures %{public}s ", GetGLErrorString());

    glBindTexture(GL_TEXTURE_EXTERNAL_OES, nativeImageTextureID_);
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "glBindTexture %{public}s ", GetGLErrorString());

    // set the texture wrapping parameters
    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_REPEAT);
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "glTexParameteri %{public}s ", GetGLErrorString());
    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_REPEAT);
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "glTexParameteri %{public}s ", GetGLErrorString());

    // set texture filtering parameters
    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "glTexParameteri %{public}s ", GetGLErrorString());
    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "glTexParameteri %{public}s ", GetGLErrorString());

    nativeImage_ = OH_NativeImage_Create(nativeImageTextureID_, GL_TEXTURE_EXTERNAL_OES);
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "OH_NativeImage_Create %{public}s ",
                  GetGLErrorString());
    if (nativeImage_ == nullptr) {
        renderContext_->MakeNoCurrent();
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosDesktop", "OH_NativeImage_Create failed.");
        return false;
    }

    nativeImageFrameAvailableListener_.context = this;
    nativeImageFrameAvailableListener_.onFrameAvailable = &OhosDesktop::OnNativeImageFrameAvailable;
    int32_t ret = OH_NativeImage_SetOnFrameAvailableListener(nativeImage_, nativeImageFrameAvailableListener_);
    if (ret != 0) {
        renderContext_->MakeNoCurrent();
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "OhosDesktop",
                     "OH_NativeImage_SetOnFrameAvailableListener failed, ret is %{public}d.", ret);
        return false;
    }

    glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "glBindTexture %{public}s ",
                  GetGLErrorString());
    renderContext_->MakeNoCurrent();
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "CreateNativeImage MakeEmptyCurrent %{public}s ",
                  GetEglErrorString());

    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "CreateNativeImage end");
    return true;
}

int32_t OhosDesktop::DestroyNativeImage() {
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "DestroyNativeImage start");
    std::lock_guard<std::mutex> lock(OhosEGLContextManage::GetInstance().GetMutex());
    renderContext_->MakeCurrent(eglSurface_);
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "DestroyNativeImage MakeCurrent %{public}s ",
                 GetEglErrorString());

    if (nativeImage_ != nullptr) {
        (void)OH_NativeImage_UnsetOnFrameAvailableListener(nativeImage_);
        OH_NativeImage_Destroy(&nativeImage_);
        nativeImage_ = nullptr;
    }

    if (nativeImageTextureID_ != 0U) {
        glDeleteTextures(1, &nativeImageTextureID_);
        nativeImageTextureID_ = 0U;
    }

    renderContext_->MakeNoCurrent();
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "DestroyNativeImage MakeEmptyCurrent %{public}s ",
                 GetEglErrorString());
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "DestroyNativeImage end");
    return 0;
}

int32_t OhosDesktop::Release() {
    OH_AVScreenCapture_Release(capture_);
    capture_ = nullptr;
    return 0;
}

OhosDesktop::~OhosDesktop() {
    Stop();
    Release();
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "OhosDesktop", "desktop release");
}

} // namespace ohos
} // namespace webrtc